home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / pine3.96.tar.gz / pine3.96.tar / pine3.96 / imap / non-ANSI / c-client / mmdf.c < prev    next >
C/C++ Source or Header  |  1996-10-15  |  68KB  |  2,248 lines

  1. /*
  2.  * Program:    MMDF mail routines
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    15 May 1993
  13.  * Last Edited:    15 October 1996
  14.  *
  15.  * Copyright 1996 by the University of Washington
  16.  *
  17.  *  Permission to use, copy, modify, and distribute this software and its
  18.  * documentation for any purpose and without fee is hereby granted, provided
  19.  * that the above copyright notice appears in all copies and that both the
  20.  * above copyright notice and this permission notice appear in supporting
  21.  * documentation, and that the name of the University of Washington not be
  22.  * used in advertising or publicity pertaining to distribution of the software
  23.  * without specific, written prior permission.  This software is made
  24.  * available "as is", and
  25.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  26.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  27.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  28.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  29.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  30.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  31.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  32.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  33.  *
  34.  */
  35.  
  36. #include <stdio.h>
  37. #include <ctype.h>
  38. #include <errno.h>
  39. extern int errno;        /* just in case */
  40. #include <signal.h>
  41. #include <sys/time.h>        /* must be before osdep.h */
  42. #include "mail.h"
  43. #include "osdep.h"
  44. #include <pwd.h>
  45. #include <sys/stat.h>
  46. #include "mmdf.h"
  47. #include "rfc822.h"
  48. #include "misc.h"
  49. #include "dummy.h"
  50.  
  51. /* MMDF mail routines */
  52.  
  53.  
  54. /* Driver dispatch used by MAIL */
  55.  
  56. DRIVER mmdfdriver = {
  57.   "mmdf",            /* driver name */
  58.   (DRIVER *) NIL,        /* next driver */
  59.   mmdf_valid,            /* mailbox is valid for us */
  60.   mmdf_parameters,        /* manipulate parameters */
  61.   mmdf_find,            /* find mailboxes */
  62.   mmdf_find_bboards,        /* find bboards */
  63.   mmdf_find_all,        /* find all mailboxes */
  64.   mmdf_find_all_bboards,    /* find all bboards */
  65.   mmdf_subscribe,        /* subscribe to mailbox */
  66.   mmdf_unsubscribe,        /* unsubscribe from mailbox */
  67.   mmdf_subscribe_bboard,    /* subscribe to bboard */
  68.   mmdf_unsubscribe_bboard,    /* unsubscribe from bboard */
  69.   mmdf_create,            /* create mailbox */
  70.   mmdf_delete,            /* delete mailbox */
  71.   mmdf_rename,            /* rename mailbox */
  72.   mmdf_open,            /* open mailbox */
  73.   mmdf_close,            /* close mailbox */
  74.   mmdf_fetchfast,        /* fetch message "fast" attributes */
  75.   mmdf_fetchflags,        /* fetch message flags */
  76.   mmdf_fetchstructure,        /* fetch message envelopes */
  77.   mmdf_fetchheader,        /* fetch message header only */
  78.   mmdf_fetchtext,        /* fetch message body only */
  79.   mmdf_fetchbody,        /* fetch message body section */
  80.   mmdf_setflag,            /* set message flag */
  81.   mmdf_clearflag,        /* clear message flag */
  82.   mmdf_search,            /* search for message based on criteria */
  83.   mmdf_ping,            /* ping mailbox to see if still alive */
  84.   mmdf_check,            /* check for new messages */
  85.   mmdf_expunge,            /* expunge deleted messages */
  86.   mmdf_copy,            /* copy messages to another mailbox */
  87.   mmdf_move,            /* move messages to another mailbox */
  88.   mmdf_append,            /* append string message to mailbox */
  89.   mmdf_gc            /* garbage collect stream */
  90. };
  91.  
  92.                 /* prototype stream */
  93. MAILSTREAM mmdfproto = {&mmdfdriver};
  94.  
  95. char *mmdfhdr = MMDFHDRTXT;    /* MMDF header */
  96.  
  97. /* MMDF mail validate mailbox
  98.  * Accepts: mailbox name
  99.  * Returns: our driver if name is valid, NIL otherwise
  100.  */
  101.  
  102. DRIVER *mmdf_valid (name)
  103.     char *name;
  104. {
  105.   char tmp[MAILTMPLEN];
  106.   return mmdf_isvalid (name,tmp) ? &mmdfdriver : NIL;
  107. }
  108.  
  109.  
  110. /* MMDF mail test for valid mailbox
  111.  * Accepts: mailbox name
  112.  * Returns: T if valid, NIL otherwise
  113.  */
  114.  
  115. int mmdf_isvalid (name,tmp)
  116.     char *name;
  117.     char *tmp;
  118. {
  119.   int fd;
  120.   int ret = NIL;
  121.   char *t,file[MAILTMPLEN];
  122.   struct stat sbuf;
  123.   time_t tp[2];
  124.   errno = EINVAL;        /* assume invalid argument */
  125.                 /* must be non-empty file */
  126.   if ((*name != '{') && !((*name == '*') && (name[1] == '{')) &&
  127.       (t = dummy_file (file,name)) && !stat (t,&sbuf)) {
  128.     if (!sbuf.st_size)errno = 0;/* empty file */
  129.     else if ((fd = open (file,O_RDONLY,NIL)) >= 0) {
  130.       memset (tmp,'\0',MAILTMPLEN);
  131.       errno = -1;        /* bogus format in case ISMMDF fails */
  132.       if (read (fd,tmp,MAILTMPLEN-1) >= 0) ret = ISMMDF (tmp) ? T : NIL;
  133.       close (fd);        /* close the file */
  134.       tp[0] = sbuf.st_atime;    /* preserve atime and mtime */
  135.       tp[1] = sbuf.st_mtime;
  136.       utime (file,tp);        /* set the times */
  137.     }
  138.   }
  139.   return ret;            /* return what we should */
  140. }
  141.  
  142. /* MMDF manipulate driver parameters
  143.  * Accepts: function code
  144.  *        function-dependent value
  145.  * Returns: function-dependent return value
  146.  */
  147.  
  148. void *mmdf_parameters (function,value)
  149.     long function;
  150.     void *value;
  151. {
  152.   return NIL;
  153. }
  154.  
  155. /* MMDF mail find list of mailboxes
  156.  * Accepts: mail stream
  157.  *        pattern to search
  158.  */
  159.  
  160. void mmdf_find (stream,pat)
  161.     MAILSTREAM *stream;
  162.     char *pat;
  163. {
  164.   if (stream) dummy_find (NIL,pat);
  165. }
  166.  
  167.  
  168. /* MMDF mail find list of bboards
  169.  * Accepts: mail stream
  170.  *        pattern to search
  171.  */
  172.  
  173. void mmdf_find_bboards (stream,pat)
  174.     MAILSTREAM *stream;
  175.     char *pat;
  176. {
  177.   if (stream) dummy_find_bboards (NIL,pat);
  178. }
  179.  
  180.  
  181. /* MMDF mail find list of all mailboxes
  182.  * Accepts: mail stream
  183.  *        pattern to search
  184.  */
  185.  
  186. void mmdf_find_all (stream,pat)
  187.     MAILSTREAM *stream;
  188.     char *pat;
  189. {
  190.   if (stream) dummy_find_all (NIL,pat);
  191. }
  192.  
  193.  
  194. /* MMDF mail find list of all bboards
  195.  * Accepts: mail stream
  196.  *        pattern to search
  197.  */
  198.  
  199. void mmdf_find_all_bboards (stream,pat)
  200.     MAILSTREAM *stream;
  201.     char *pat;
  202. {
  203.   if (stream) dummy_find_all_bboards (NIL,pat);
  204. }
  205.  
  206. /* MMDF mail subscribe to mailbox
  207.  * Accepts: mail stream
  208.  *        mailbox to add to subscription list
  209.  * Returns: T on success, NIL on failure
  210.  */
  211.  
  212. long mmdf_subscribe (stream,mailbox)
  213.     MAILSTREAM *stream;
  214.     char *mailbox;
  215. {
  216.   char tmp[MAILTMPLEN];
  217.   return sm_subscribe (dummy_file (tmp,mailbox));
  218. }
  219.  
  220.  
  221. /* MMDF mail unsubscribe to mailbox
  222.  * Accepts: mail stream
  223.  *        mailbox to delete from subscription list
  224.  * Returns: T on success, NIL on failure
  225.  */
  226.  
  227. long mmdf_unsubscribe (stream,mailbox)
  228.     MAILSTREAM *stream;
  229.     char *mailbox;
  230. {
  231.   char tmp[MAILTMPLEN];
  232.   return sm_unsubscribe (dummy_file (tmp,mailbox));
  233. }
  234.  
  235.  
  236. /* MMDF mail subscribe to bboard
  237.  * Accepts: mail stream
  238.  *        bboard to add to subscription list
  239.  * Returns: T on success, NIL on failure
  240.  */
  241.  
  242. long mmdf_subscribe_bboard (stream,mailbox)
  243.     MAILSTREAM *stream;
  244.     char *mailbox;
  245. {
  246.   return NIL;            /* never valid for MMDF */
  247. }
  248.  
  249.  
  250. /* MMDF mail unsubscribe to bboard
  251.  * Accepts: mail stream
  252.  *        bboard to delete from subscription list
  253.  * Returns: T on success, NIL on failure
  254.  */
  255.  
  256. long mmdf_unsubscribe_bboard (stream,mailbox)
  257.     MAILSTREAM *stream;
  258.     char *mailbox;
  259. {
  260.   return NIL;            /* never valid for MMDF */
  261. }
  262.  
  263. /* MMDF mail create mailbox
  264.  * Accepts: MAIL stream
  265.  *        mailbox name to create
  266.  * Returns: T on success, NIL on failure
  267.  */
  268.  
  269. long mmdf_create (stream,mailbox)
  270.     MAILSTREAM *stream;
  271.     char *mailbox;
  272. {
  273.   return dummy_create (stream,mailbox);
  274. }
  275.  
  276.  
  277. /* MMDF mail delete mailbox
  278.  * Accepts: MAIL stream
  279.  *        mailbox name to delete
  280.  * Returns: T on success, NIL on failure
  281.  */
  282.  
  283. long mmdf_delete (stream,mailbox)
  284.     MAILSTREAM *stream;
  285.     char *mailbox;
  286. {
  287.   return mmdf_rename (stream,mailbox,NIL);
  288. }
  289.  
  290. /* MMDF mail rename mailbox
  291.  * Accepts: MAIL stream
  292.  *        old mailbox name
  293.  *        new mailbox name (or NIL for delete)
  294.  * Returns: T on success, NIL on failure
  295.  */
  296.  
  297. long mmdf_rename (stream,old,new)
  298.     MAILSTREAM *stream;
  299.     char *old;
  300.     char *new;
  301. {
  302.   long ret = T;
  303.   char tmp[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN],lockx[MAILTMPLEN];
  304.   int fd,ld;
  305.                 /* get the c-client lock */
  306.   if (!lockname (lock,dummy_file (file,old))) return NIL;
  307.   if ((ld = open (lock,O_RDWR|O_CREAT,
  308.           (int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL))) < 0) {
  309.     sprintf (tmp,"Can't get lock for mailbox %s: %s",old,strerror (errno));
  310.     mm_log (tmp,ERROR);
  311.     return NIL;
  312.   }
  313.                 /* lock out other c-clients */
  314.   if (flock (ld,LOCK_EX|LOCK_NB)) {
  315.     close (ld);            /* couldn't lock, give up on it then */
  316.     sprintf (tmp,"Mailbox %s is in use by another process",old);
  317.     mm_log (tmp,ERROR);
  318.     return NIL;
  319.   }
  320.                 /* lock out non c-client applications */
  321.   if ((fd = mmdf_lock (file,O_RDWR,S_IREAD|S_IWRITE,lockx,LOCK_EX)) < 0) {
  322.     sprintf (tmp,"Can't lock mailbox %s: %s",old,strerror (errno));
  323.     mm_log (tmp,ERROR);
  324.     return NIL;
  325.   }
  326.                 /* do the rename or delete operation */
  327.   if (new ? rename (file,dummy_file (tmp,new)) : unlink (file)) {
  328.     sprintf (tmp,"Can't %s mailbox %s: %s",new ? "rename" : "delete",old,
  329.          strerror (errno));
  330.     mm_log (tmp,ERROR);
  331.     ret = NIL;            /* set failure */
  332.   }
  333.   mmdf_unlock (fd,NIL,lockx);    /* unlock and close mailbox */
  334.   flock (ld,LOCK_UN);        /* release c-client lock lock */
  335.   close (ld);            /* close c-client lock */
  336.   unlink (lock);        /* and delete it */
  337.   return ret;            /* return success */
  338. }
  339.  
  340. /* MMDF mail open
  341.  * Accepts: Stream to open
  342.  * Returns: Stream on success, NIL on failure
  343.  */
  344.  
  345. MAILSTREAM *mmdf_open (stream)
  346.     MAILSTREAM *stream;
  347. {
  348.   long i;
  349.   int fd;
  350.   char tmp[MAILTMPLEN];
  351.   struct stat sbuf;
  352.   long retry;
  353.                 /* return prototype for OP_PROTOTYPE call */
  354.   if (!stream) return &mmdfproto;
  355.   retry = stream->silent ? 1 : KODRETRY;
  356.   if (LOCAL) {            /* close old file if stream being recycled */
  357.     mmdf_close (stream);    /* dump and save the changes */
  358.     stream->dtb = &mmdfdriver;/* reattach this driver */
  359.     mail_free_cache (stream);    /* clean up cache */
  360.   }
  361.   stream->local = fs_get (sizeof (MMDFLOCAL));
  362.                 /* canonicalize the stream mailbox name */
  363.   dummy_file (tmp,stream->mailbox);
  364.                 /* force readonly if bboard */
  365.   if (*stream->mailbox == '*') stream->rdonly = T;
  366.   else {            /* canonicalize name */
  367.     fs_give ((void **) &stream->mailbox);
  368.     stream->mailbox = cpystr (tmp);
  369.   }
  370.   /* You may wonder why LOCAL->name is needed.  It isn't at all obvious from
  371.    * the code.  The problem is that when a stream is recycled with another
  372.    * mailbox of the same type, the driver's close method isn't called because
  373.    * it could be IMAP and closing then would defeat the entire point of
  374.    * recycling.  Hence there is code in the file drivers to call the close
  375.    * method such as what appears above.  The problem is, by this point,
  376.    * mail_open() has already changed the stream->mailbox name to point to the
  377.    * new name, and mmdf_close() needs the old name.
  378.    */
  379.   LOCAL->name = cpystr (tmp);    /* local copy for recycle case */
  380.   LOCAL->ld = NIL;        /* no state locking yet */
  381.   LOCAL->lname = NIL;
  382.   LOCAL->filesize = 0;        /* initialize file information */
  383.   LOCAL->filetime = 0;
  384.   LOCAL->msgs = NIL;        /* no cache yet */
  385.   LOCAL->cachesize = 0;
  386.   LOCAL->buf = (char *) fs_get ((LOCAL->buflen = CHUNK) + 1);
  387.   stream->sequence++;        /* bump sequence number */
  388.  
  389.   LOCAL->dirty = NIL;        /* no update yet */
  390.                 /* make lock for read/write access */
  391.   if (!stream->rdonly) while (retry) {
  392.                 /* get a new file handle each time */
  393.     if (!lockname (tmp,LOCAL->name) ||
  394.     ((fd = open (tmp,O_RDWR|O_CREAT,
  395.             (int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL))) < 0)){
  396.       mm_log ("Can't open mailbox lock, access is readonly",WARN);
  397.       retry = 0;        /* give up */
  398.     }
  399.                 /* can get the lock? */
  400.     else if (flock (fd,LOCK_EX|LOCK_NB)) {
  401.       if (retry-- == KODRETRY) {/* no, first time through? */
  402.                 /* yes, get other process' PID */
  403.     if (!fstat (fd,&sbuf) && (i = min (sbuf.st_size,MAILTMPLEN)) &&
  404.         (read (fd,tmp,i) == i) && !(tmp[i] = 0) && (i = atol (tmp))) {
  405.       kill ((int) i,SIGUSR2);
  406.       sprintf (tmp,"Trying to get mailbox lock from process %ld",i);
  407.       mm_log (tmp,WARN);
  408.     }
  409.     else retry = 0;        /* give up */
  410.       }
  411.       close (fd);        /* get a new handle next time around */
  412.       if (!stream->silent) {    /* nothing if silent stream */
  413.     if (retry) sleep (1);    /* wait a second before trying again */
  414.     else mm_log ("Mailbox is open by another process, access is readonly",
  415.              WARN);
  416.       }
  417.     }
  418.     else {            /* got the lock, nobody else can alter state */
  419.       LOCAL->ld = fd;        /* note lock's fd and name */
  420.       LOCAL->lname = cpystr (tmp);
  421.                 /* make sure mode OK (don't use fchmod()) */
  422.       chmod (LOCAL->lname,(int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
  423.       if (stream->silent) i = 0;/* silent streams won't accept KOD */
  424.       else {            /* note our PID in the lock */
  425.     sprintf (tmp,"%d",getpid ());
  426.     write (fd,tmp,(i = strlen (tmp))+1);
  427.       }
  428.       ftruncate (fd,i);        /* make sure tied off */
  429.       fsync (fd);        /* make sure it's available */
  430.       retry = 0;        /* no more need to try */
  431.     }
  432.   }
  433.  
  434.                 /* parse mailbox */
  435.   stream->nmsgs = stream->recent = 0;
  436.                 /* will we be able to get write access? */
  437.   if (LOCAL->ld && access (LOCAL->name,W_OK) && (errno == EACCES)) {
  438.     mm_log ("Can't get write access to mailbox, access is readonly",WARN);
  439.     flock (LOCAL->ld,LOCK_UN);    /* release the lock */
  440.     close (LOCAL->ld);        /* close the lock file */
  441.     LOCAL->ld = NIL;        /* no more lock fd */
  442.     unlink (LOCAL->lname);    /* delete it */
  443.     fs_give ((void **) &LOCAL->lname);
  444.   }
  445.                 /* abort if can't get RW silent stream */
  446.   if (stream->silent && !stream->rdonly && !LOCAL->ld) mmdf_abort (stream);
  447.                 /* parse mailbox */
  448.   else if ((fd = mmdf_parse (stream,tmp,LOCK_SH)) >= 0) {
  449.     mmdf_unlock (fd,stream,tmp);
  450.     mail_unlock (stream);
  451.   }
  452.   if (!LOCAL) return NIL;    /* failure if stream died */
  453.   stream->rdonly = !LOCAL->ld;    /* make sure upper level knows readonly */
  454.                 /* notify about empty mailbox */
  455.   if (!(stream->nmsgs || stream->silent)) mm_log ("Mailbox is empty",NIL);
  456.   return stream;        /* return stream alive to caller */
  457. }
  458.  
  459. /* MMDF mail close
  460.  * Accepts: MAIL stream
  461.  */
  462.  
  463. void mmdf_close (stream)
  464.     MAILSTREAM *stream;
  465. {
  466.   int silent = stream->silent;
  467.   stream->silent = T;        /* note this stream is dying */
  468.   mmdf_check (stream);        /* dump final checkpoint */
  469.   stream->silent = silent;    /* restore previous status */
  470.   mmdf_abort (stream);        /* now punt the file and local data */
  471. }
  472.  
  473.  
  474. /* MMDF mail fetch fast information
  475.  * Accepts: MAIL stream
  476.  *        sequence
  477.  */
  478.  
  479. void mmdf_fetchfast (stream,sequence)
  480.     MAILSTREAM *stream;
  481.     char *sequence;
  482. {
  483.   return;            /* no-op for local mail */
  484. }
  485.  
  486.  
  487. /* MMDF mail fetch flags
  488.  * Accepts: MAIL stream
  489.  *        sequence
  490.  */
  491.  
  492. void mmdf_fetchflags (stream,sequence)
  493.     MAILSTREAM *stream;
  494.     char *sequence;
  495. {
  496.   return;            /* no-op for local mail */
  497. }
  498.  
  499. /* MMDF mail fetch structure
  500.  * Accepts: MAIL stream
  501.  *        message # to fetch
  502.  *        pointer to return body
  503.  * Returns: envelope of this message, body returned in body value
  504.  *
  505.  * Fetches the "fast" information as well
  506.  */
  507.  
  508. ENVELOPE *mmdf_fetchstructure (stream,msgno,body)
  509.     MAILSTREAM *stream;
  510.     long msgno;
  511.     BODY **body;
  512. {
  513.   ENVELOPE **env;
  514.   BODY **b;
  515.   STRING bs;
  516.   LONGCACHE *lelt;
  517.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  518.   long i = max (m->headersize,m->bodysize);
  519.   if (stream->scache) {        /* short cache */
  520.     if (msgno != stream->msgno){/* flush old poop if a different message */
  521.       mail_free_envelope (&stream->env);
  522.       mail_free_body (&stream->body);
  523.     }
  524.     stream->msgno = msgno;
  525.     env = &stream->env;        /* get pointers to envelope and body */
  526.     b = &stream->body;
  527.   }
  528.   else {            /* long cache */
  529.     lelt = mail_lelt (stream,msgno);
  530.     env = &lelt->env;        /* get pointers to envelope and body */
  531.     b = &lelt->body;
  532.   }
  533.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  534.     mail_free_envelope (env);    /* flush old envelope and body */
  535.     mail_free_body (b);
  536.     if (i > LOCAL->buflen) {    /* make sure enough buffer space */
  537.       fs_give ((void **) &LOCAL->buf);
  538.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = i) + 1);
  539.     }
  540.     INIT (&bs,mail_string,(void *) m->body,m->bodysize);
  541.                 /* parse envelope and body */
  542.     rfc822_parse_msg (env,body ? b : NIL,m->header,m->headersize,&bs,
  543.               mylocalhost (),LOCAL->buf);
  544.   }
  545.   if (body) *body = *b;        /* return the body */
  546.   return *env;            /* return the envelope */
  547. }
  548.  
  549. /* MMDF mail fetch message header
  550.  * Accepts: MAIL stream
  551.  *        message # to fetch
  552.  * Returns: message header in RFC822 format
  553.  */
  554.  
  555. char *mmdf_fetchheader (stream,msgno)
  556.     MAILSTREAM *stream;
  557.     long msgno;
  558. {
  559.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  560.                 /* copy the string */
  561.   strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,m->header,m->headersize);
  562.   return LOCAL->buf;
  563. }
  564.  
  565.  
  566. /* MMDF mail fetch message text (only)
  567.     body only;
  568.  * Accepts: MAIL stream
  569.  *        message # to fetch
  570.  * Returns: message text in RFC822 format
  571.  */
  572.  
  573. char *mmdf_fetchtext (stream,msgno)
  574.     MAILSTREAM *stream;
  575.     long msgno;
  576. {
  577.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  578.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  579.   if (!elt->seen) {        /* if message not seen */
  580.     elt->seen = T;        /* mark message as seen */
  581.                 /* recalculate Status/X-Status lines */
  582.     mmdf_update_status (m->status,elt);
  583.     LOCAL->dirty = T;        /* note stream is now dirty */
  584.   }
  585.   strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,m->body,m->bodysize);
  586.   return LOCAL->buf;
  587. }
  588.  
  589. /* MMDF fetch message body as a structure
  590.  * Accepts: Mail stream
  591.  *        message # to fetch
  592.  *        section specifier
  593.  *        pointer to length
  594.  * Returns: pointer to section of message body
  595.  */
  596.  
  597. char *mmdf_fetchbody (stream,m,s,len)
  598.     MAILSTREAM *stream;
  599.     long m;
  600.     char *s;
  601.     unsigned long *len;
  602. {
  603.   BODY *b;
  604.   PART *pt;
  605.   unsigned long i;
  606.   char *base = LOCAL->msgs[m - 1]->body;
  607.   unsigned long offset = 0;
  608.   MESSAGECACHE *elt = mail_elt (stream,m);
  609.                 /* make sure have a body */
  610.   if (!(mmdf_fetchstructure (stream,m,&b) && b && s && *s &&
  611.     ((i = strtol (s,&s,10)) > 0))) return NIL;
  612.   do {                /* until find desired body part */
  613.                 /* multipart content? */
  614.     if (b->type == TYPEMULTIPART) {
  615.       pt = b->contents.part;    /* yes, find desired part */
  616.       while (--i && (pt = pt->next));
  617.       if (!pt) return NIL;    /* bad specifier */
  618.                 /* note new body, check valid nesting */
  619.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  620.       offset = pt->offset;    /* get new offset */
  621.     }
  622.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  623.                 /* need to go down further? */
  624.     if (i = *s) switch (b->type) {
  625.     case TYPEMESSAGE:        /* embedded message */
  626.       offset = b->contents.msg.offset;
  627.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  628.     case TYPEMULTIPART:        /* multipart, get next section */
  629.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  630.     default:            /* bogus subpart specification */
  631.       return NIL;
  632.     }
  633.   } while (i);
  634.                 /* lose if body bogus */
  635.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  636.   if (!elt->seen) {        /* if message not seen */
  637.     elt->seen = T;        /* mark message as seen */
  638.                 /* recalculate Status/X-Status lines */
  639.     mmdf_update_status (LOCAL->msgs[m - 1]->status,elt);
  640.     LOCAL->dirty = T;        /* note stream is now dirty */
  641.   }
  642.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  643.               b->size.ibytes,b->encoding);
  644. }
  645.  
  646. /* MMDF mail set flag
  647.  * Accepts: MAIL stream
  648.  *        sequence
  649.  *        flag(s)
  650.  */
  651.  
  652. void mmdf_setflag (stream,sequence,flag)
  653.     MAILSTREAM *stream;
  654.     char *sequence;
  655.     char *flag;
  656. {
  657.   MESSAGECACHE *elt;
  658.   long i;
  659.   short f = mmdf_getflags (stream,flag);
  660.   if (!f) return;        /* no-op if no flags to modify */
  661.                 /* get sequence and loop on it */
  662.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  663.     if ((elt = mail_elt (stream,i))->sequence) {
  664.                 /* set all requested flags */
  665.       if (f&fSEEN) elt->seen = T;
  666.       if (f&fDELETED) elt->deleted = T;
  667.       if (f&fFLAGGED) elt->flagged = T;
  668.       if (f&fANSWERED) elt->answered = T;
  669.                 /* recalculate Status/X-Status lines */
  670.       mmdf_update_status (LOCAL->msgs[i - 1]->status,elt);
  671.       LOCAL->dirty = T;        /* note stream is now dirty */
  672.     }
  673. }
  674.  
  675.  
  676. /* MMDF mail clear flag
  677.  * Accepts: MAIL stream
  678.  *        sequence
  679.  *        flag(s)
  680.  */
  681.  
  682. void mmdf_clearflag (stream,sequence,flag)
  683.     MAILSTREAM *stream;
  684.     char *sequence;
  685.     char *flag;
  686. {
  687.   MESSAGECACHE *elt;
  688.   long i;
  689.   short f = mmdf_getflags (stream,flag);
  690.   if (!f) return;        /* no-op if no flags to modify */
  691.                 /* get sequence and loop on it */
  692.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  693.     if ((elt = mail_elt (stream,i))->sequence) {
  694.                 /* clear all requested flags */
  695.       if (f&fSEEN) elt->seen = NIL;
  696.       if (f&fDELETED) elt->deleted = NIL;
  697.       if (f&fFLAGGED) elt->flagged = NIL;
  698.       if (f&fANSWERED) elt->answered = NIL;
  699.                 /* recalculate Status/X-Status lines */
  700.       mmdf_update_status (LOCAL->msgs[i - 1]->status,elt);
  701.       LOCAL->dirty = T;        /* note stream is now dirty */
  702.     }
  703. }
  704.  
  705. /* MMDF mail search for messages
  706.  * Accepts: MAIL stream
  707.  *        search criteria
  708.  */
  709.  
  710. void mmdf_search (stream,criteria)
  711.     MAILSTREAM *stream;
  712.     char *criteria;
  713. {
  714.   long i,n;
  715.   char *d;
  716.   search_t f;
  717.                 /* initially all searched */
  718.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  719.                 /* get first criterion */
  720.   if (criteria && (criteria = strtok (criteria," "))) {
  721.                 /* for each criterion */
  722.     for (; criteria; (criteria = strtok (NIL," "))) {
  723.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  724.       switch (*ucase (criteria)) {
  725.       case 'A':            /* possible ALL, ANSWERED */
  726.     if (!strcmp (criteria+1,"LL")) f = mmdf_search_all;
  727.     else if (!strcmp (criteria+1,"NSWERED")) f = mmdf_search_answered;
  728.     break;
  729.       case 'B':            /* possible BCC, BEFORE, BODY */
  730.     if (!strcmp (criteria+1,"CC"))
  731.       f = mmdf_search_string (mmdf_search_bcc,&d,&n);
  732.     else if (!strcmp (criteria+1,"EFORE"))
  733.       f = mmdf_search_date (mmdf_search_before,&n);
  734.     else if (!strcmp (criteria+1,"ODY"))
  735.       f = mmdf_search_string (mmdf_search_body,&d,&n);
  736.     break;
  737.       case 'C':            /* possible CC */
  738.     if (!strcmp (criteria+1,"C"))
  739.       f = mmdf_search_string (mmdf_search_cc,&d,&n);
  740.     break;
  741.       case 'D':            /* possible DELETED */
  742.     if (!strcmp (criteria+1,"ELETED")) f = mmdf_search_deleted;
  743.     break;
  744.       case 'F':            /* possible FLAGGED, FROM */
  745.     if (!strcmp (criteria+1,"LAGGED")) f = mmdf_search_flagged;
  746.     else if (!strcmp (criteria+1,"ROM"))
  747.       f = mmdf_search_string (mmdf_search_from,&d,&n);
  748.     break;
  749.       case 'K':            /* possible KEYWORD */
  750.     if (!strcmp (criteria+1,"EYWORD"))
  751.       f = mmdf_search_flag (mmdf_search_keyword,&d);
  752.     break;
  753.       case 'N':            /* possible NEW */
  754.     if (!strcmp (criteria+1,"EW")) f = mmdf_search_new;
  755.     break;
  756.  
  757.       case 'O':            /* possible OLD, ON */
  758.     if (!strcmp (criteria+1,"LD")) f = mmdf_search_old;
  759.     else if (!strcmp (criteria+1,"N"))
  760.       f = mmdf_search_date (mmdf_search_on,&n);
  761.     break;
  762.       case 'R':            /* possible RECENT */
  763.     if (!strcmp (criteria+1,"ECENT")) f = mmdf_search_recent;
  764.     break;
  765.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  766.     if (!strcmp (criteria+1,"EEN")) f = mmdf_search_seen;
  767.     else if (!strcmp (criteria+1,"INCE"))
  768.       f = mmdf_search_date (mmdf_search_since,&n);
  769.     else if (!strcmp (criteria+1,"UBJECT"))
  770.       f = mmdf_search_string (mmdf_search_subject,&d,&n);
  771.     break;
  772.       case 'T':            /* possible TEXT, TO */
  773.     if (!strcmp (criteria+1,"EXT"))
  774.       f = mmdf_search_string (mmdf_search_text,&d,&n);
  775.     else if (!strcmp (criteria+1,"O"))
  776.       f = mmdf_search_string (mmdf_search_to,&d,&n);
  777.     break;
  778.       case 'U':            /* possible UN* */
  779.     if (criteria[1] == 'N') {
  780.       if (!strcmp (criteria+2,"ANSWERED")) f = mmdf_search_unanswered;
  781.       else if (!strcmp (criteria+2,"DELETED")) f = mmdf_search_undeleted;
  782.       else if (!strcmp (criteria+2,"FLAGGED")) f = mmdf_search_unflagged;
  783.       else if (!strcmp (criteria+2,"KEYWORD"))
  784.         f = mmdf_search_flag (mmdf_search_unkeyword,&d);
  785.       else if (!strcmp (criteria+2,"SEEN")) f = mmdf_search_unseen;
  786.     }
  787.     break;
  788.       default:            /* we will barf below */
  789.     break;
  790.       }
  791.       if (!f) {            /* if can't determine any criteria */
  792.     sprintf (LOCAL->buf,"Unknown search criterion: %.30s",criteria);
  793.     mm_log (LOCAL->buf,ERROR);
  794.     return;
  795.       }
  796.                 /* run the search criterion */
  797.       for (i = 1; i <= stream->nmsgs; ++i)
  798.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  799.       mail_elt (stream,i)->searched = NIL;
  800.     }
  801.                 /* report search results to main program */
  802.     for (i = 1; i <= stream->nmsgs; ++i)
  803.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  804.   }
  805. }
  806.  
  807. /* MMDF mail ping mailbox
  808.  * Accepts: MAIL stream
  809.  * Returns: T if stream alive, else NIL
  810.  * No-op for readonly files, since read/writer can expunge it from under us!
  811.  */
  812.  
  813. long mmdf_ping (stream)
  814.     MAILSTREAM *stream;
  815. {
  816.   char lock[MAILTMPLEN];
  817.   struct stat sbuf;
  818.   int fd;
  819.                 /* does he want to give up readwrite? */
  820.   if (stream->rdonly && LOCAL->ld) {
  821.     flock (LOCAL->ld,LOCK_UN);    /* yes, release the lock */
  822.     close (LOCAL->ld);        /* close the lock file */
  823.     LOCAL->ld = NIL;        /* no more lock fd */
  824.     unlink (LOCAL->lname);    /* delete it */
  825.     fs_give ((void **) &LOCAL->lname);
  826.   }
  827.                 /* make sure it is alright to do this at all */
  828.   if (LOCAL && LOCAL->ld && !stream->lock) {
  829.                 /* get current mailbox size */
  830.     stat (LOCAL->name,&sbuf);    /* parse if mailbox changed */
  831.     if ((sbuf.st_size != LOCAL->filesize) &&
  832.     ((fd = mmdf_parse (stream,lock,LOCK_SH)) >= 0)) {
  833.                 /* unlock mailbox */
  834.       mmdf_unlock (fd,stream,lock);
  835.       mail_unlock (stream);    /* and stream */
  836.     }
  837.   }
  838.   return LOCAL ? T : NIL;    /* return if still alive */
  839. }
  840.  
  841. /* MMDF mail check mailbox
  842.  * Accepts: MAIL stream
  843.  * No-op for readonly files, since read/writer can expunge it from under us!
  844.  */
  845.  
  846. void mmdf_check (stream)
  847.     MAILSTREAM *stream;
  848. {
  849.   char lock[MAILTMPLEN];
  850.   int fd;
  851.                 /* parse and lock mailbox */
  852.   if (LOCAL && LOCAL->ld && ((fd = mmdf_parse (stream,lock,LOCK_EX)) >= 0)) {
  853.                 /* dump checkpoint if needed */
  854.     if (LOCAL->dirty && mmdf_extend (stream,fd,NIL)) mmdf_save (stream,fd);
  855.                 /* flush locks */
  856.     mmdf_unlock (fd,stream,lock);
  857.     mail_unlock (stream);
  858.   }
  859.   if (LOCAL && LOCAL->ld && !stream->silent) mm_log ("Check completed",NIL);
  860. }
  861.  
  862. /* MMDF mail expunge mailbox
  863.  * Accepts: MAIL stream
  864.  */
  865.  
  866. void mmdf_expunge (stream)
  867.     MAILSTREAM *stream;
  868. {
  869.   int fd,j;
  870.   long i = 1;
  871.   long n = 0;
  872.   unsigned long recent;
  873.   MESSAGECACHE *elt;
  874.   char *r = "No messages deleted, so no update needed";
  875.   char lock[MAILTMPLEN];
  876.   if (LOCAL && LOCAL->ld) {    /* parse and lock mailbox */
  877.     if ((fd = mmdf_parse (stream,lock,LOCK_EX)) >= 0) {
  878.       recent = stream->recent;    /* get recent now that new ones parsed */
  879.       while ((j = (i<=stream->nmsgs)) && !(elt = mail_elt (stream,i))->deleted)
  880.     i++;            /* find first deleted message */
  881.       if (j) {            /* found one? */
  882.                 /* make sure we can do the worst case thing */
  883.     if (mmdf_extend (stream,fd,"Unable to expunge mailbox")) {
  884.       do {            /* flush deleted messages */
  885.         if ((elt = mail_elt (stream,i))->deleted) {
  886.                 /* if recent, note one less recent message */
  887.           if (elt->recent) --recent;
  888.                 /* flush local cache entry */
  889.           fs_give ((void **) &LOCAL->msgs[i - 1]);
  890.           for (j = i; j < stream->nmsgs; j++)
  891.         LOCAL->msgs[j - 1] = LOCAL->msgs[j];
  892.           LOCAL->msgs[stream->nmsgs - 1] = NIL;
  893.                 /* notify upper levels */
  894.           mail_expunged (stream,i);
  895.           n++;        /* count another expunged message */
  896.         }
  897.         else i++;        /* otherwise try next message */
  898.       } while (i <= stream->nmsgs);
  899.                 /* dump checkpoint of the results */
  900.       mmdf_save (stream,fd);
  901.       sprintf ((r = LOCAL->buf),"Expunged %d messages",n);
  902.     }
  903.       }
  904.                 /* notify upper level, free locks */
  905.       mail_exists (stream,stream->nmsgs);
  906.       mail_recent (stream,recent);
  907.       mmdf_unlock (fd,stream,lock);
  908.       mail_unlock (stream);
  909.     }
  910.   }
  911.   else r = "Expunge ignored on readonly mailbox";
  912.   if (LOCAL && !stream->silent) mm_log (r,NIL);
  913. }
  914.  
  915. /* MMDF mail copy message(s)
  916.     s;
  917.  * Accepts: MAIL stream
  918.  *        sequence
  919.  *        destination mailbox
  920.  * Returns: T if copy successful, else NIL
  921.  */
  922.  
  923. long mmdf_copy (stream,sequence,mailbox)
  924.     MAILSTREAM *stream;
  925.     char *sequence;
  926.     char *mailbox;
  927. {
  928.                 /* copy the messages */
  929.   return (mail_sequence (stream,sequence)) ?
  930.     mmdf_copy_messages (stream,mailbox) : NIL;
  931. }
  932.  
  933.  
  934. /* MMDF mail move message(s)
  935.     s;
  936.  * Accepts: MAIL stream
  937.  *        sequence
  938.  *        destination mailbox
  939.  * Returns: T if move successful, else NIL
  940.  */
  941.  
  942. long mmdf_move (stream,sequence,mailbox)
  943.     MAILSTREAM *stream;
  944.     char *sequence;
  945.     char *mailbox;
  946. {
  947.   long i;
  948.   MESSAGECACHE *elt;
  949.   if (!(mail_sequence (stream,sequence) &&
  950.     mmdf_copy_messages (stream,mailbox))) return NIL;
  951.                 /* delete all requested messages */
  952.   for (i = 1; i <= stream->nmsgs; i++)
  953.     if ((elt = mail_elt (stream,i))->sequence) {
  954.       elt->deleted = T;        /* mark message deleted */
  955.                 /* recalculate Status/X-Status lines */
  956.       mmdf_update_status (LOCAL->msgs[i - 1]->status,elt);
  957.       LOCAL->dirty = T;        /* note stream is now dirty */
  958.     }
  959.   return T;
  960. }
  961.  
  962. /* MMDF mail append message from stringstruct
  963.  * Accepts: MAIL stream
  964.  *        destination mailbox
  965.  *        stringstruct of messages to append
  966.  * Returns: T if append successful, else NIL
  967.  */
  968.  
  969. long mmdf_append (stream,mailbox,flags,date,message)
  970.     MAILSTREAM *stream;
  971.     char *mailbox;
  972.     char *flags;
  973.     char *date;
  974.                STRING *message;
  975. {
  976.   struct stat sbuf;
  977.   int fd,ok = T;
  978.   char c,buf[MAILTMPLEN],file[MAILTMPLEN],lock[MAILTMPLEN];
  979.   time_t tp[2];
  980.   MESSAGECACHE elt;
  981.   int i = 0;
  982.   char *s = buf;
  983.   time_t t = time (0);
  984.   long size = SIZE (message);
  985.   short f = mmdf_getflags (stream,flags);
  986.   if (date) {            /* want to preserve date? */
  987.                 /* yes, parse date into an elt */
  988.     if (!mail_parse_date (&elt,date)) {
  989.       sprintf (buf,"Bad date in append: %s",date);
  990.       mm_log (buf,ERROR);
  991.       return NIL;
  992.     }
  993.   }
  994.                 /* make sure valid mailbox */
  995.   if (!mmdf_isvalid (mailbox,buf)) switch (errno) {
  996.   case ENOENT:            /* no such file? */
  997.     mm_notify (stream,"[TRYCREATE] Must create mailbox before append",NIL);
  998.     return NIL;
  999.   case 0:            /* merely empty file? */
  1000.     break;
  1001.   case EINVAL:
  1002.     sprintf (buf,"Invalid MMDF-format mailbox name: %s",mailbox);
  1003.     mm_log (buf,ERROR);
  1004.     return NIL;
  1005.   default:
  1006.     sprintf (buf,"Not a MMDF-format mailbox: %s",mailbox);
  1007.     mm_log (buf,ERROR);
  1008.     return NIL;
  1009.   }
  1010.   if ((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  1011.                S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  1012.     sprintf (buf,"Can't open append mailbox: %s",strerror (errno));
  1013.     mm_log (buf,ERROR);
  1014.     return NIL;
  1015.   }
  1016.   mm_critical (stream);        /* go critical */
  1017.   fstat (fd,&sbuf);        /* get current file size */
  1018.   sprintf (buf,"%sFrom %s@%s ",mmdfhdr,myusername (),mylocalhost ());
  1019.                 /* write the date given */
  1020.   if (date) mail_cdate (buf + strlen (buf),&elt);
  1021.   else strcat (buf,ctime (&t));    /* otherwise write the time now */
  1022.   sprintf (buf + strlen (buf),"Status: %sO\nX-Status: %s%s%s\n",
  1023.        f&fSEEN ? "R" : "",f&fDELETED ? "D" : "",
  1024.        f&fFLAGGED ? "F" : "",f&fANSWERED ? "A" : "");
  1025.                 /* write header */
  1026.   if (write (fd,buf,strlen (buf)) < 0) {
  1027.     sprintf (buf,"Header write failed: %s",strerror (errno));
  1028.     mm_log (buf,ERROR);
  1029.     ftruncate (fd,sbuf.st_size);
  1030.     ok = NIL;
  1031.   }
  1032.   else while (size--) {        /* copy text, tossing out CR's and CTRL/A */
  1033.     if (((c = SNX (message)) != '\015') && (c != MMDFCHR)) s[i++] = c;
  1034.                 /* dump if filled buffer or no more data */
  1035.     if ((!size) || (i == MAILTMPLEN)) {
  1036.       if ((write (fd,buf,i)) < 0) {
  1037.     sprintf (buf,"Message append failed: %s",strerror (errno));
  1038.     mm_log (buf,ERROR);
  1039.     ftruncate (fd,sbuf.st_size);
  1040.     ok = NIL;
  1041.       }
  1042.       i = 0;            /* restart buffer */
  1043.                 /* write out final newline if at end */
  1044.       if (!size) write (fd,"\n",1);
  1045.     }
  1046.   }
  1047.   if (fsync (fd)) {        /* force out the update */
  1048.     sprintf (LOCAL->buf,"Unable to sync mailbox: %s",strerror (errno));
  1049.     mm_log (LOCAL->buf,WARN);
  1050.     ftruncate (fd,sbuf.st_size);
  1051.     ok = NIL;
  1052.   }
  1053.   tp[0] = sbuf.st_atime;    /* preserve atime */
  1054.   tp[1] = time (0);        /* set mtime to now */
  1055.   utime (file,tp);        /* set the times */
  1056.   mmdf_unlock (fd,NIL,lock);    /* unlock and close mailbox */
  1057.   mm_nocritical (stream);    /* release critical */
  1058.   return ok;            /* return success */
  1059. }
  1060.  
  1061. /* MMDF garbage collect stream
  1062.  * Accepts: Mail stream
  1063.  *        garbage collection flags
  1064.  */
  1065.  
  1066. void mmdf_gc (stream,gcflags)
  1067.     MAILSTREAM *stream;
  1068.     long gcflags;
  1069. {
  1070.   /* nothing here for now */
  1071. }
  1072.  
  1073. /* Internal routines */
  1074.  
  1075.  
  1076. /* MMDF mail abort stream
  1077.  * Accepts: MAIL stream
  1078.  */
  1079.  
  1080. void mmdf_abort (stream)
  1081.     MAILSTREAM *stream;
  1082. {
  1083.   long i;
  1084.   if (LOCAL) {            /* only if a file is open */
  1085.     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
  1086.     if (LOCAL->ld) {        /* have a mailbox lock? */
  1087.       flock (LOCAL->ld,LOCK_UN);/* yes, release the lock */
  1088.       close (LOCAL->ld);    /* close the lock file */
  1089.       unlink (LOCAL->lname);    /* and delete it */
  1090.     }
  1091.     fs_give ((void **) &LOCAL->lname);
  1092.     if (LOCAL->msgs) {        /* free local cache */
  1093.       for (i = 0; i < stream->nmsgs; ++i) fs_give ((void **) &LOCAL->msgs[i]);
  1094.       fs_give ((void **) &LOCAL->msgs);
  1095.     }
  1096.                 /* free local text buffers */
  1097.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  1098.                 /* nuke the local data */
  1099.     fs_give ((void **) &stream->local);
  1100.     stream->dtb = NIL;        /* log out the DTB */
  1101.   }
  1102. }
  1103.  
  1104. /* MMDF open and lock mailbox
  1105.  * Accepts: file name to open/lock
  1106.  *        file open mode
  1107.  *        destination buffer for lock file name
  1108.  *        type of locking operation (LOCK_SH or LOCK_EX)
  1109.  */
  1110.  
  1111. int mmdf_lock (file,flags,mode,lock,op)
  1112.     char *file;
  1113.     int flags;
  1114.     int mode;
  1115.     char *lock;
  1116.     int op;
  1117. {
  1118.   int fd,ld,j;
  1119.   int i = LOCKTIMEOUT * 60 - 1;
  1120.   char hitch[MAILTMPLEN],tmp[MAILTMPLEN];
  1121.   time_t t;
  1122.   struct stat sb;
  1123.                 /* build lock filename */
  1124.   if (chk_notsymlink (strcat (dummy_file (lock,file),".lock"),NIL)) do {
  1125.     t = time (0);        /* get the time now */
  1126. #ifdef NFSKLUDGE
  1127.   /* SUN-OS had an NFS, As kludgy as an albatross;
  1128.    * And everywhere that it was installed, It was a total loss.  -- MRC 9/25/91
  1129.    */
  1130.                 /* build hitching post file name */
  1131.     sprintf (hitch,"%s.%d.%d.",lock,time (0),getpid ());
  1132.     j = strlen (hitch);        /* append local host name */
  1133.     gethostname (hitch + j,(MAILTMPLEN - j) - 1);
  1134.                 /* try to get hitching-post file */
  1135.     if ((ld = open (hitch,O_WRONLY|O_CREAT|O_EXCL,
  1136.             (int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL))) < 0) {
  1137.       sprintf (tmp,"Error creating %s: %s",hitch,strerror (errno));
  1138.       switch (errno) {        /* what happened? */
  1139.       case EEXIST:        /* file already exists? */
  1140.     break;            /* oops, just try again */
  1141.       case EACCES:        /* protection failure */
  1142.                 /* try again if file exists(?) */
  1143.     if (!stat (hitch,&sb)) break;
  1144.                 /* punt silently if paranoid site */
  1145.     if (!mail_parameters (NIL,GET_LOCKEACCESERROR,NIL))
  1146.       default:            /* some other error */
  1147.       mm_log (tmp,WARN);    /* this is probably not good */
  1148.     *lock = '\0';        /* give up on lock file */
  1149.     break;
  1150.       }
  1151.     }
  1152.     else {            /* got a hitching-post */
  1153.                 /* make sure others can break the lock */
  1154.       chmod (hitch,(int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
  1155.       close (ld);        /* close the hitching-post */
  1156.       link (hitch,lock);    /* tie hitching-post to lock, ignore failure */
  1157.       stat (hitch,&sb);        /* get its data */
  1158.       unlink (hitch);        /* flush hitching post */
  1159.       /* If link count .ne. 2, hitch failed.  Set ld to -1 as if open() failed
  1160.      so we try again.  If extant lock file and time now is .gt. file time
  1161.      plus timeout interval, flush the lock so can win next time around. */
  1162.       if ((ld = (sb.st_nlink != 2) ? -1 : 0) && (!stat (lock,&sb)) &&
  1163.       (t > sb.st_ctime + LOCKTIMEOUT * 60)) unlink (lock);
  1164.     }
  1165.  
  1166. #else
  1167.   /* This works on modern Unix systems which are not afflicted with NFS mail.
  1168.    * "Modern" means that O_EXCL works.  I think that NFS mail is a terrible
  1169.    * idea -- that's what IMAP is for -- but some people insist upon losing...
  1170.    */
  1171.                 /* try to get the lock */
  1172.     if ((ld = open (lock,O_WRONLY|O_CREAT|O_EXCL,
  1173.             (int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL))) < 0)
  1174.       switch (errno) {        /* what happened? */
  1175.       case EEXIST:        /* if extant and old, grab it for ourselves */
  1176.     if ((!stat (lock,&sb)) && t > sb.st_ctime + LOCKTIMEOUT * 60)
  1177.       ld = open (lock,O_WRONLY|O_CREAT,
  1178.              (int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
  1179.     break;
  1180.       case EACCES:        /* protection fail, ignore if non-ex or old */
  1181.     if (!mail_parameters (NIL,GET_LOCKEACCESERROR,NIL)) {
  1182.       if (stat (lock,&sb) || (t > sb.st_ctime + LOCKTIMEOUT * 60))
  1183.         *lock = '\0';    /* assume no world write mail spool dir */
  1184.       break;
  1185.     }
  1186.       default:            /* some other failure */
  1187.     sprintf (tmp,"Error creating %s: %s",lock,strerror (errno));
  1188.     mm_log (tmp,WARN);    /* this is probably not good */
  1189.     *lock = '\0';        /* don't use lock files */
  1190.     break;
  1191.       }
  1192.     if (ld >= 0) {        /* if made a lock file */
  1193.                 /* make sure others can break the lock */
  1194.       chmod (lock,(int) mail_parameters (NIL,GET_LOCKPROTECTION,NIL));
  1195.       close (ld);        /* close the lock file */
  1196.     }
  1197. #endif
  1198.     if ((ld < 0) && *lock) {    /* if failed to make lock file and retry OK */
  1199.       if (!(i%15)) {
  1200.     sprintf (tmp,"Mailbox %s is locked, will override in %d seconds...",
  1201.          file,i);
  1202.     mm_log (tmp,WARN);
  1203.       }
  1204.       sleep (1);        /* wait 1 second before next try */
  1205.     }
  1206.   } while (i-- && ld < 0 && *lock);
  1207.                 /* open file */
  1208.   if ((fd = open (file,flags,mode)) >= 0) flock (fd,op);
  1209.   else {            /* open failed */
  1210.     j = errno;            /* preserve error code */
  1211.     if (*lock) unlink (lock);    /* flush the lock file if any */
  1212.     errno = j;            /* restore error code */
  1213.   }
  1214.   return fd;
  1215. }
  1216.  
  1217. /* MMDF unlock and close mailbox
  1218.  * Accepts: file descriptor
  1219.  *        (optional) mailbox stream to check atime/mtime
  1220.  *        (optional) lock file name
  1221.  */
  1222.  
  1223. void mmdf_unlock (fd,stream,lock)
  1224.     int fd;
  1225.     MAILSTREAM *stream;
  1226.     char *lock;
  1227. {
  1228.   struct stat sbuf;
  1229.   time_t tp[2];
  1230.   fstat (fd,&sbuf);        /* get file times */
  1231.                 /* if stream and csh would think new mail */
  1232.   if (stream && (sbuf.st_atime <= sbuf.st_mtime)) {
  1233.     tp[0] = time (0);        /* set atime to now */
  1234.                 /* set mtime to (now - 1) if necessary */
  1235.     tp[1] = tp[0] > sbuf.st_mtime ? sbuf.st_mtime : tp[0] - 1;
  1236.                 /* set the times, note change */
  1237.     if (!utime (LOCAL->name,tp)) LOCAL->filetime = tp[1];
  1238.   }
  1239.   flock (fd,LOCK_UN);        /* release flock'ers */
  1240.   close (fd);            /* close the file */
  1241.                 /* flush the lock file if any */
  1242.   if (lock && *lock) unlink (lock);
  1243. }
  1244.  
  1245. /* MMDF mail parse and lock mailbox
  1246.  * Accepts: MAIL stream
  1247.  *        space to write lock file name
  1248.  *        type of locking operation
  1249.  * Returns: file descriptor if parse OK, mailbox is locked shared
  1250.  *        -1 if failure, stream aborted
  1251.  */
  1252.  
  1253. int mmdf_parse (stream,lock,op)
  1254.     MAILSTREAM *stream;
  1255.     char *lock;
  1256.     int op;
  1257. {
  1258.   int fd;
  1259.   long delta,i,j,is,is1;
  1260.   char *s,*s1,*t = NIL,*e;
  1261.   int ti = 0,zn = 0;
  1262.   int first = T;
  1263.   long nmsgs = stream->nmsgs;
  1264.   long newcnt = 0;
  1265.   struct tm *tm;
  1266.   struct stat sbuf;
  1267.   STRING bs;
  1268.   MESSAGECACHE *elt;
  1269.   FILECACHE *m = NIL,*n = NIL;
  1270.   mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL);
  1271.   mail_lock (stream);        /* guard against recursion or pingers */
  1272.                 /* open and lock mailbox (shared OK) */
  1273.   if ((fd = mmdf_lock (LOCAL->name,LOCAL->ld ? O_RDWR : O_RDONLY,NIL,
  1274.              lock,op)) < 0) {
  1275.     sprintf (LOCAL->buf,"Mailbox open failed, aborted: %s",strerror (errno));
  1276.     mm_log (LOCAL->buf,ERROR);
  1277.     mmdf_abort (stream);
  1278.     mail_unlock (stream);
  1279.     return -1;
  1280.   }
  1281.   fstat (fd,&sbuf);        /* get status */
  1282.                 /* calculate change in size */
  1283.   if ((delta = sbuf.st_size - LOCAL->filesize) < 0) {
  1284.     sprintf (LOCAL->buf,"Mailbox shrank from %d to %d bytes, aborted",
  1285.          LOCAL->filesize,sbuf.st_size);
  1286.     mm_log (LOCAL->buf,ERROR);    /* this is pretty bad */
  1287.     mmdf_unlock (fd,stream,lock);
  1288.     mmdf_abort (stream);
  1289.     mail_unlock (stream);
  1290.     return -1;
  1291.   }
  1292.  
  1293.   else if (delta) {        /* get to that position in the file */
  1294.     lseek (fd,LOCAL->filesize,L_SET);
  1295.     s = s1 = LOCAL->buf;    /* initial read-in location */
  1296.     i = 0;            /* initial unparsed read-in count */
  1297.     do {
  1298.       i = min (CHUNK,delta);    /* calculate read-in size */
  1299.                 /* increase the read-in buffer if necessary */
  1300.       if ((j = i + (s1 - s)) >= LOCAL->buflen) {
  1301.     is = s - LOCAL->buf;    /* note former start of message position */
  1302.     is1 = s1 - LOCAL->buf;    /* and start of new data position */
  1303.     if (s1 - s) fs_resize ((void **) &LOCAL->buf,(LOCAL->buflen = j) + 1);
  1304.     else {            /* fs_resize would do an unnecessary copy */
  1305.       fs_give ((void **) &LOCAL->buf);
  1306.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = j) + 1);
  1307.     }
  1308.     s = LOCAL->buf + is;    /* new start of message */
  1309.     s1 = LOCAL->buf + is1;    /* new start of new data */
  1310.       }
  1311.       s1[i] = '\0';        /* tie off chunk */
  1312.       if (read (fd,s1,i) < 0) {    /* read a chunk of new text */
  1313.     sprintf (LOCAL->buf,"Error reading mail file: %s",strerror (errno));
  1314.     mm_log (LOCAL->buf,ERROR);
  1315.     mmdf_unlock (fd,stream,lock);
  1316.     mmdf_abort (stream);
  1317.     mail_unlock (stream);
  1318.     return -1;
  1319.       }
  1320.       delta -= i;        /* account for data read in */
  1321.                 /* validate newly-appended data */
  1322.       if (first) {        /* only do this first time! */
  1323.     if (*s == '\n') s++;    /* skip spurious newline */
  1324.     if (!ISMMDF (s)) {    /* must start with MMDF header */
  1325.       char tmp[MAILTMPLEN];
  1326.       sprintf (tmp,
  1327.            "Unexpected changes to mailbox (try restarting): %.20s",s);
  1328.       mm_log (tmp,ERROR);
  1329.       mmdf_unlock (fd,stream,lock);
  1330.       mmdf_abort (stream);
  1331.       mail_unlock (stream);
  1332.       return -1;
  1333.     }
  1334.     s += MMDFHDRLEN;    /* skip over MMDF header */
  1335.     i -= MMDFHDRLEN;
  1336.     if (s1 < s) s1 = s;
  1337.     first = NIL;        /* don't do this again */
  1338.       }
  1339.  
  1340.                 /* found end of message or end of data? */
  1341.       while (i && ((e = mmdf_eom (s,s1,i)) || !delta)) {
  1342.     if (e != s) {        /* make sure not another header */
  1343.       nmsgs++;        /* yes, have a new message */
  1344.       /* Note: unlike bezerk, the final newline is part of the message,
  1345.          hence length is 1 larger than in bezerk driver. */
  1346.                 /* calculate message length */
  1347.       j = ((e ? e : s1 + i) - s);
  1348.       if (m) {        /* new cache needed, have previous data? */
  1349.         n->header = (char *) fs_get (sizeof (FILECACHE) + j + 3);
  1350.         n = (FILECACHE *) n->header;
  1351.       }
  1352.       else m = n = (FILECACHE *) fs_get (sizeof (FILECACHE) + j + 3);
  1353.                 /* copy message data */
  1354.       memcpy (n->internal,s,j);
  1355.                 /* ensure ends with newline */
  1356.       if (s[j-1] != '\n') n->internal[j++] = '\n';
  1357.       n->internal[j] = '\0';
  1358.       n->header = NIL;    /* initially no link */
  1359.       n->headersize = j;    /* stash away buffer length */
  1360.     }
  1361.     if (e) {        /* saw end of message? */
  1362.       e += MMDFHDRLEN;    /* skip over MMDF trailer */
  1363.                 /* new unparsed data count */
  1364.       if (((i -= e - s1) >= 5) && ISMMDF (e)) {
  1365.         e += MMDFHDRLEN;    /* skip next message's MMDF header too */
  1366.         i -= MMDFHDRLEN;
  1367.       }
  1368.       s = s1 = e;        /* advance to new message */
  1369.     }
  1370.     else break;        /* else punt this loop */
  1371.       }
  1372.       if (delta) {        /* end of message not found? */
  1373.     s1 += i;        /* end of unparsed data */
  1374.     if (s != LOCAL->buf){    /* message doesn't begin at buffer? */
  1375.       i = s1 - s;        /* length of message so far */
  1376.       memmove (LOCAL->buf,s,i);
  1377.       s = LOCAL->buf;    /* message now starts at buffer origin */
  1378.       s1 = s + i;        /* calculate new end of unparsed data */
  1379.     }
  1380.       }
  1381.     } while (delta);        /* until nothing more new to read */
  1382.   }
  1383.   else {            /* no change, don't babble if never got time */
  1384.     if (LOCAL->filetime && LOCAL->filetime != sbuf.st_mtime)
  1385.       mm_log ("New mailbox modification time but apparently no changes",WARN);
  1386.   }
  1387.   (*mc) (stream,nmsgs,CH_SIZE);    /* expand the primary cache */
  1388.   if (nmsgs>=LOCAL->cachesize) {/* need to expand cache? */
  1389.                 /* number of messages plus room to grow */
  1390.     LOCAL->cachesize = nmsgs + CACHEINCREMENT;
  1391.     if (LOCAL->msgs)        /* resize if already have a cache */
  1392.       fs_resize ((void **) &LOCAL->msgs,LOCAL->cachesize*sizeof (FILECACHE *));
  1393.     else LOCAL->msgs =        /* create new cache */
  1394.       (FILECACHE **) fs_get (LOCAL->cachesize * sizeof (FILECACHE *));
  1395.   }
  1396.  
  1397.   if (LOCAL->buflen > CHUNK) {    /* maybe move where the buffer is in memory*/
  1398.     fs_give ((void **) &LOCAL->buf);
  1399.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = CHUNK) + 1);
  1400.   }
  1401.   for (i = stream->nmsgs, n = m; i < nmsgs; i++) {
  1402.     LOCAL->msgs[i] = m = n;    /* set cache, and next cache pointer */
  1403.     n = (FILECACHE *) n->header;
  1404.     ti = NIL;            /* valid header not found */
  1405.     if (s = m->internal) VALID (s,t,ti,zn);
  1406.     if (ti) {            /* found a valid header? */
  1407.                 /* pointer to message header */
  1408.       if (s = strchr (t++,'\n')) m->header = ++s;
  1409.       else {            /* probably totally empty message */
  1410.     strcat (t-1,"\n");    /* append newline */
  1411.     m->headersize++;    /* adjust count */
  1412.     m->header = s = strchr (t-1,'\n') + 1;
  1413.       }
  1414.     }
  1415.     else m->header = s;        /* assume no internal header here */
  1416.     m->headersize -= m->header - m->internal;
  1417.     m->body = NIL;        /* assume no body as yet */
  1418.     m->bodysize = 0;
  1419.     newcnt++;            /* assume recent by default */
  1420.     (elt = mail_elt (stream,i+1))->recent = T;
  1421.                 /* calculate initial Status/X-Status lines */
  1422.     mmdf_update_status (m->status,elt);
  1423.  
  1424.     if (ti) {            /* generate plausable IMAPish date string */
  1425.       LOCAL->buf[2] = LOCAL->buf[6] = LOCAL->buf[20] = '-';
  1426.       LOCAL->buf[11] = ' ';
  1427.       LOCAL->buf[14] = LOCAL->buf[17] = ':';
  1428.                 /* dd */
  1429.       LOCAL->buf[0] = t[ti - 3]; LOCAL->buf[1] = t[ti - 2];
  1430.                 /* mmm */
  1431.       LOCAL->buf[3] = t[ti - 7]; LOCAL->buf[4] = t[ti - 6];
  1432.       LOCAL->buf[5] = t[ti - 5];
  1433.                 /* hh */
  1434.       LOCAL->buf[12] = t[ti]; LOCAL->buf[13] = t[ti + 1];
  1435.                 /* mm */
  1436.       LOCAL->buf[15] = t[ti + 3]; LOCAL->buf[16] = t[ti + 4];
  1437.       if (t[ti += 5] == ':') {    /* ss if present */
  1438.     LOCAL->buf[18] = t[++ti];
  1439.     LOCAL->buf[19] = t[++ti];
  1440.     ti++;            /* move to space */
  1441.       }
  1442.       else LOCAL->buf[18] = LOCAL->buf[19] = '0';
  1443.                 /* yy -- advance over timezone if necessary */
  1444.       if (zn == ++ti) ti += (((t[zn] == '+') || (t[zn] == '-')) ? 6 : 4);
  1445.       LOCAL->buf[7] = t[ti]; LOCAL->buf[8] = t[ti + 1];
  1446.       LOCAL->buf[9] = t[ti + 2]; LOCAL->buf[10] = t[ti + 3];
  1447.                 /* zzz */
  1448.       t = zn ? (t + zn) : "LCL";
  1449.       LOCAL->buf[21] = *t++; LOCAL->buf[22] = *t++; LOCAL->buf[23] = *t++;
  1450.       if ((LOCAL->buf[21] != '+') && (LOCAL->buf[21] != '-'))
  1451.     LOCAL->buf[24] = '\0';
  1452.       else {            /* numeric time zone */
  1453.     LOCAL->buf[24] = *t++; LOCAL->buf[25] = *t++;
  1454.     LOCAL->buf[26] = '\0'; LOCAL->buf[20] = ' ';
  1455.       }
  1456.                 /* set internal date */
  1457.       if (!mail_parse_date (elt,LOCAL->buf)) mm_log ("Unparsable date",WARN);
  1458.     }
  1459.     else {            /* make date from file date */
  1460.       tm = gmtime (&sbuf.st_mtime);
  1461.       elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  1462.       elt->year = tm->tm_year + 1900 - BASEYEAR;
  1463.       elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  1464.       elt->seconds = tm->tm_sec;
  1465.       elt->zhours = 0; elt->zminutes = 0;
  1466.     }
  1467.  
  1468.     e = NIL;            /* no status stuff yet */
  1469.     do switch (*(t = s)) {    /* look at header lines */
  1470.     case '\n':            /* end of header */
  1471.       m->body = ++s;        /* start of body is here */
  1472.       j = m->body - m->header;    /* new header size */
  1473.                 /* calculate body size */
  1474.       m->bodysize = (j <= m->headersize) ? m->headersize - j : 0;
  1475.       if (e) {            /* saw status poop? */
  1476.     *e++ = '\n';        /* patch in trailing newline */
  1477.     m->headersize = e - m->header;
  1478.       }
  1479.       else m->headersize = j;    /* set header size */
  1480.       s = NIL;            /* don't scan any further */
  1481.       break;
  1482.     case '\0':            /* end of message */
  1483.       if (e) {            /* saw status poop? */
  1484.     *e++ = '\n';        /* patch in trailing newline */
  1485.     m->headersize = e - m->header;
  1486.       }
  1487.       break;
  1488.  
  1489.     case 'X':            /* possible X-Status: line */
  1490.       if (s[1] == '-' && s[2] == 'S' && s[3] == 't' && s[4] == 'a' &&
  1491.       s[5] == 't' && s[6] == 'u' && s[7] == 's' && s[8] == ':') s += 2;
  1492.     case 'S':            /* possible Status: line */
  1493.       if (s[0] == 'S' && s[1] == 't' && s[2] == 'a' && s[3] == 't' &&
  1494.       s[4] == 'u' && s[5] == 's' && s[6] == ':') {
  1495.     if (!e) e = t;        /* note deletion point */
  1496.     s += 6;            /* advance to status flags */
  1497.     do switch (*s++) {    /* parse flags */
  1498.     case 'R':        /* message read */
  1499.       elt->seen = T;
  1500.       break;
  1501.     case 'O':        /* message old */
  1502.       if (elt->recent) {    /* don't do this more than once! */
  1503.         elt->recent = NIL;    /* not recent any longer... */
  1504.         newcnt--;
  1505.       }
  1506.       break;
  1507.     case 'D':        /* message deleted */
  1508.       elt->deleted = T;
  1509.       break;
  1510.     case 'F':        /* message flagged */
  1511.       elt ->flagged = T;
  1512.       break;
  1513.     case 'A':        /* message answered */
  1514.       elt ->answered = T;
  1515.       break;
  1516.     default:        /* some other crap */
  1517.       break;
  1518.     } while (*s && *s != '\n');
  1519.                 /* recalculate Status/X-Status lines */
  1520.     mmdf_update_status (m->status,elt);
  1521.     break;            /* all done */
  1522.       }
  1523.                 /* otherwise fall into default case */
  1524.  
  1525.     default:            /* anything else is uninteresting */
  1526.       if (e) {            /* have status stuff to worry about? */
  1527.     j = s - e;        /* yuck!!  calculate size of delete area */
  1528.                 /* blat remaining number of bytes down */
  1529.     memmove (e,s,m->header + m->headersize - s);
  1530.     m->headersize -= j;    /* update for new size */
  1531.     s = e;            /* back up pointer */
  1532.     e = NIL;        /* no more delete area */
  1533.                 /* tie off old cruft */
  1534.     *(m->header + m->headersize) = '\0';
  1535.       }
  1536.       break;
  1537.     } while (s && (s = strchr (s,'\n')) && s++);
  1538.                 /* get size including CR's  */
  1539.     INIT (&bs,mail_string,(void *) m->header,m->headersize);
  1540.     elt->rfc822_size = strcrlflen (&bs);
  1541.     INIT (&bs,mail_string,(void *) m->body,m->bodysize);
  1542.     elt->rfc822_size += strcrlflen (&bs);
  1543.   }
  1544.   if (n) fatal ("Cache link-list inconsistency");
  1545.   while (i < LOCAL->cachesize) LOCAL->msgs[i++] = NIL;
  1546.                 /* update parsed file size and time */
  1547.   LOCAL->filesize = sbuf.st_size;
  1548.   LOCAL->filetime = sbuf.st_mtime;
  1549.   mail_exists (stream,nmsgs);    /* notify upper level of new mailbox size */
  1550.   mail_recent (stream,stream->recent + newcnt);
  1551.   if (newcnt) LOCAL->dirty = T;    /* mark dirty so O flags are set */
  1552.   return fd;            /* return the winnage */
  1553. }
  1554.  
  1555. /* MMDF search for end of message
  1556.  * Accepts: start of message
  1557.  *        start of new data
  1558.  *        size of new data
  1559.  * Returns: pointer to start of new message if one found
  1560.  */
  1561.  
  1562. #define Word unsigned long
  1563.  
  1564. char *mmdf_eom (som,sod,i)
  1565.     char *som;
  1566.     char *sod;
  1567.     long i;
  1568. {
  1569.   char *s = (sod > som) ? sod - 1 : sod;
  1570.   union {
  1571.     unsigned long wd;
  1572.     char ch[9];
  1573.   } wdtest;
  1574.                 /* move back *two* lines */
  1575.   while ((s > som) && *s-- != '\n');
  1576.   while ((s > som) && *s-- != '\n');
  1577.   if (i > MMDFHDRLEN + 8) {    /* don't do fast search if very few bytes */
  1578.                 /* constant for word testing */
  1579.     strcpy (wdtest.ch,"AAAA1234");
  1580.     if(wdtest.wd == 0x41414141){/* do it fast way if on a 32-bit machine */
  1581.                 /* any characters before word boundary? */
  1582.       for (; ((long) s & 3); s++) if (ISMMDF (s)) return s;
  1583.       i = (sod + i) - s;    /* total number of tries */
  1584.       do {            /* word-at-a-time search for CTRL/A */
  1585.     if (0x80808080&(0x01010101+(0x7f7f7f7f&~(MMDFCHRS^*(Word *) s))))
  1586.       RETIFMMDFWRD (s);    /* found it! */
  1587.     s += 4;            /* try next word */
  1588.     i -= 4;            /* count a word checked */
  1589.       } while (i > 8);        /* continue until near the end */
  1590.     }
  1591.   }
  1592.   while (i >= MMDFHDRLEN) {
  1593.     if (ISMMDF (s)) return s;
  1594.     i--; s++;
  1595.   }
  1596.   return NIL;
  1597. }
  1598.  
  1599. /* MMDF extend mailbox to reserve worst-case space for expansion
  1600.  * Accepts: MAIL stream
  1601.  *        file descriptor
  1602.  *        error string
  1603.  * Returns: T if extend OK and have gone critical, NIL if should abort
  1604.  */
  1605.  
  1606. int mmdf_extend (stream,fd,error)
  1607.     MAILSTREAM *stream;
  1608.     int fd;
  1609.     char *error;
  1610. {
  1611.   struct stat sbuf;
  1612.   MESSAGECACHE *elt;
  1613.   FILECACHE *m;
  1614.   char tmp[MAILTMPLEN];
  1615.   int i,ok;
  1616.   long f;
  1617.   char *s;
  1618.   int retry;
  1619.                 /* calculate estimated size of mailbox */
  1620.   for (i = 0,f = 0; i < stream->nmsgs;) {
  1621.     m = LOCAL->msgs[i];        /* get cache pointer */
  1622.     elt = mail_elt (stream,++i);/* get elt, increment message */
  1623.                 /* if not expunging, or not deleted */
  1624.     if (!(error && elt->deleted))
  1625.       f += MMDFHDRLEN + (m->header - m->internal) + m->headersize +
  1626.     sizeof (STATUS) - (elt->seen+elt->deleted+elt->flagged+elt->answered) +
  1627.       m->bodysize + 1 + MMDFHDRLEN;
  1628.   }
  1629.   mm_critical (stream);        /* go critical */
  1630.                 /* return now if file large enough */
  1631.   if (f <= LOCAL->filesize) return T;
  1632.   s = (char *) fs_get (f -= LOCAL->filesize);
  1633.   memset (s,0,f);        /* get a block of nulls */
  1634.                 /* get to end of file */
  1635.   lseek (fd,LOCAL->filesize,L_SET);
  1636.   do {
  1637.     retry = NIL;        /* no retry yet */
  1638.     if (!(ok = (write (fd,s,f) >= 0))) {
  1639.       i = errno;        /* note error before doing ftruncate */
  1640.                 /* restore prior file size */
  1641.       ftruncate (fd,LOCAL->filesize);
  1642.       fsync (fd);        /* is this necessary? */
  1643.       fstat (fd,&sbuf);        /* now get updated file time */
  1644.       LOCAL->filetime = sbuf.st_mtime;
  1645.                 /* punt if that's what main program wants */
  1646.       if (mm_diskerror (stream,i,NIL)) {
  1647.     mm_nocritical (stream);    /* exit critical */
  1648.     sprintf (tmp,"%s: %s",error ? error : "Unable to update mailbox",
  1649.          strerror (i));
  1650.     mm_notify (stream,tmp,WARN);
  1651.       }
  1652.       else retry = T;        /* set to retry */
  1653.     }
  1654.   } while (retry);        /* repeat if need to try again */
  1655.   fs_give ((void **) &s);    /* flush buffer of nulls */
  1656.   return ok;            /* return status */
  1657. }
  1658.  
  1659. /* MMDF save mailbox
  1660.  * Accepts: MAIL stream
  1661.  *        mailbox file descriptor
  1662.  *
  1663.  * Mailbox must be readwrite and locked for exclusive access.
  1664.  */
  1665.  
  1666. void mmdf_save (stream,fd)
  1667.     MAILSTREAM *stream;
  1668.     int fd;
  1669. {
  1670.   struct stat sbuf;
  1671.   long i;
  1672.   int e;
  1673.   int retry;
  1674.   do {                /* restart point if failure */
  1675.     retry = NIL;        /* no need to retry yet */
  1676.                 /* start at beginning of file */
  1677.     lseek (fd,LOCAL->filesize = 0,L_SET);
  1678.                 /* loop through all messages */
  1679.     for (i = 1; i <= stream->nmsgs; i++) {
  1680.                 /* write message */
  1681.       if ((e = mmdf_write_message (fd,LOCAL->msgs[i-1])) < 0) {
  1682.     sprintf (LOCAL->buf,"Mailbox rewrite error: %s",strerror (e = errno));
  1683.     mm_log (LOCAL->buf,WARN);
  1684.     mm_diskerror (stream,e,T);
  1685.     retry = T;        /* must retry */
  1686.     break;            /* abort this particular try */
  1687.       }
  1688.       else LOCAL->filesize += e;/* count these bytes in data */
  1689.     }
  1690.     if (fsync (fd)) {        /* make sure the updates take */
  1691.       sprintf (LOCAL->buf,"Unable to sync mailbox: %s",strerror (e = errno));
  1692.       mm_log (LOCAL->buf,WARN);
  1693.       mm_diskerror (stream,e,T);
  1694.       retry = T;        /* oops */
  1695.     }
  1696.   } while (retry);        /* repeat if need to try again */
  1697.   ftruncate(fd,LOCAL->filesize);/* nuke any cruft after that */
  1698.   fsync (fd);            /* is this necessary? */
  1699.   fstat (fd,&sbuf);        /* now get updated file time */
  1700.   LOCAL->filetime = sbuf.st_mtime;
  1701.   LOCAL->dirty = NIL;        /* stream no longer dirty */
  1702.   mm_nocritical (stream);    /* exit critical */
  1703. }
  1704.  
  1705. /* MMDF copy messages
  1706.  * Accepts: MAIL stream
  1707.  *        mailbox name
  1708.  * Returns: T if copy successful else NIL
  1709.  */
  1710.  
  1711. int mmdf_copy_messages (stream,mailbox)
  1712.     MAILSTREAM *stream;
  1713.     char *mailbox;
  1714. {
  1715.   char file[MAILTMPLEN],lock[MAILTMPLEN];
  1716.   int fd;
  1717.   struct stat sbuf;
  1718.   time_t tp[2];
  1719.   long i;
  1720.   int ok = T;
  1721.                 /* make sure valid mailbox */
  1722.   if (!mmdf_isvalid (mailbox,file)) switch (errno) {
  1723.   case ENOENT:            /* no such file? */
  1724.     mm_notify (stream,"[TRYCREATE] Must create mailbox before copy",NIL);
  1725.     return NIL;
  1726.   case 0:            /* merely empty file? */
  1727.     break;
  1728.   case EINVAL:
  1729.     sprintf (LOCAL->buf,"Invalid MMDF-format mailbox name: %s",mailbox);
  1730.     mm_log (LOCAL->buf,ERROR);
  1731.     return NIL;
  1732.   default:
  1733.     sprintf (LOCAL->buf,"Not a MMDF-format mailbox: %s",mailbox);
  1734.     mm_log (LOCAL->buf,ERROR);
  1735.     return NIL;
  1736.   }
  1737.   if ((fd = mmdf_lock (dummy_file (file,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  1738.                S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  1739.     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  1740.     mm_log (LOCAL->buf,ERROR);
  1741.     return NIL;
  1742.   }
  1743.   mm_critical (stream);        /* go critical */
  1744.   fstat (fd,&sbuf);        /* get current file size */
  1745.                 /* write all requested messages to mailbox */
  1746.   for (i = 1; ok && i <= stream->nmsgs; i++)
  1747.                 /* write message if selected */
  1748.     if (mail_elt (stream,i)->sequence &&
  1749.     (mmdf_write_message (fd,LOCAL->msgs[i - 1]) < 0)) {
  1750.       sprintf (LOCAL->buf,"Message copy failed: %s",strerror (errno));
  1751.       mm_log (LOCAL->buf,ERROR);
  1752.       ftruncate (fd,sbuf.st_size);
  1753.       ok = NIL;
  1754.       break;
  1755.     }
  1756.   if (fsync (fd)) {        /* force out the update */
  1757.     sprintf (LOCAL->buf,"Message copy sync failed: %s",strerror (errno));
  1758.     mm_log (LOCAL->buf,ERROR);
  1759.     ftruncate (fd,sbuf.st_size);
  1760.     ok = NIL;            /* oops */
  1761.   }
  1762.   tp[0] = sbuf.st_atime;    /* preserve atime */
  1763.   tp[1] = time (0);        /* set mtime to now */
  1764.   utime (file,tp);        /* set the times */
  1765.   mmdf_unlock (fd,NIL,lock);    /* unlock and close mailbox */
  1766.   mm_nocritical (stream);    /* release critical */
  1767.   return ok;            /* return whether or not succeeded */
  1768. }
  1769.  
  1770. /* MMDF write message to mailbox
  1771.  * Accepts: file descriptor
  1772.  *        local cache for this message
  1773.  * Returns: number of bytes written or -1 if error
  1774.  *
  1775.  * This routine is the reason why the local cache has a copy of the status.
  1776.  * We can be called to dump out the mailbox as part of a stream recycle, since
  1777.  * we don't write out the mailbox when flags change and hence an update may be
  1778.  * needed.  However, at this point the elt has already become history, so we
  1779.  * can't use any information other than what is local to us.
  1780.  */
  1781.  
  1782. int mmdf_write_message (fd,m)
  1783.     int fd;
  1784.     FILECACHE *m;
  1785. {
  1786.   struct iovec iov[16];
  1787.   int i = 0;
  1788.   long siz = (m->header + m->headersize) - m->internal;
  1789.   iov[i].iov_base = mmdfhdr;    /* write MMDF header */
  1790.   iov[i++].iov_len = MMDFHDRLEN;
  1791.   if (siz) {            /* allow for the case of no headers */
  1792.     iov[i].iov_base=m->internal;/* pointer/counter to headers */
  1793.     iov[i].iov_len = siz;    /* length of internal + message headers */
  1794.                 /* suppress extra newline if present */
  1795.     if (((char *) iov[i].iov_base)[iov[i].iov_len - 2] == '\n')
  1796.       iov[i++].iov_len--;
  1797.     else i++;            /* unlikely but... */
  1798.   }
  1799.   iov[i].iov_base = m->status;    /* pointer/counter to status */
  1800.   iov[i++].iov_len = strlen (m->status);
  1801.   if (m->bodysize) {        /* only if a non-empty body */
  1802.     iov[i].iov_base = m->body;    /* pointer/counter to text body */
  1803.     iov[i++].iov_len = m->bodysize;
  1804.     if (m->body[m->bodysize - 1] != '\n') {
  1805.       iov[i].iov_base = "\n";    /* pointer/counter to extra newline */
  1806.       iov[i++].iov_len = 1;
  1807.     }
  1808.   }
  1809.   iov[i].iov_base = mmdfhdr;    /* write MMDF trailer */
  1810.   iov[i++].iov_len = MMDFHDRLEN;
  1811.   return writev (fd,iov,i);
  1812. }
  1813.  
  1814.  
  1815. /* MMDF update status string
  1816.  * Accepts: destination string to write
  1817.  *        message cache entry
  1818.  */
  1819.  
  1820. void mmdf_update_status (status,elt)
  1821.     char *status;
  1822.     MESSAGECACHE *elt;
  1823. {
  1824.   /* This used to be an sprintf(), but thanks to certain cretinous C libraries
  1825.      with horribly slow implementations of sprintf() I had to change it to this
  1826.      mess.  At least it should be fast. */
  1827.   char *t = status + 8;
  1828.   status[0] = 'S'; status[1] = 't'; status[2] = 'a'; status[3] = 't';
  1829.   status[4] = 'u'; status[5] = 's'; status[6] = ':';  status[7] = ' ';
  1830.   if (elt->seen) *t++ = 'R'; *t++ = 'O'; *t++ = '\n';
  1831.   *t++ = 'X'; *t++ = '-'; *t++ = 'S'; *t++ = 't'; *t++ = 'a'; *t++ = 't';
  1832.   *t++ = 'u'; *t++ = 's'; *t++ = ':'; *t++ = ' ';
  1833.   if (elt->deleted) *t++ = 'D'; if (elt->flagged) *t++ = 'F';
  1834.   if (elt->answered) *t++ = 'A';
  1835.   *t++ = '\n'; *t++ = '\n'; *t++ = '\0';
  1836. }
  1837.  
  1838. /* Parse flag list
  1839.  * Accepts: MAIL stream
  1840.  *        flag list as a character string
  1841.  * Returns: flag command list
  1842.  */
  1843.  
  1844.  
  1845. short mmdf_getflags (stream,flag)
  1846.     MAILSTREAM *stream;
  1847.     char *flag;
  1848. {
  1849.   char *t,tmp[MAILTMPLEN],err[MAILTMPLEN];
  1850.   short f = 0;
  1851.   short i,j;
  1852.   if (flag && *flag) {        /* no-op if no flag string */
  1853.                 /* check if a list and make sure valid */
  1854.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  1855.       mm_log ("Bad flag list",ERROR);
  1856.       return NIL;
  1857.     }
  1858.                 /* copy the flag string w/o list construct */
  1859.     strncpy (tmp,flag+i,(j = strlen (flag) - (2*i)));
  1860.     tmp[j] = '\0';
  1861.     t = ucase (tmp);        /* uppercase only from now on */
  1862.  
  1863.     while (t && *t) {        /* parse the flags */
  1864.       if (*t == '\\') {        /* system flag? */
  1865.     switch (*++t) {        /* dispatch based on first character */
  1866.     case 'S':        /* possible \Seen flag */
  1867.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  1868.       t += 4;        /* skip past flag name */
  1869.       break;
  1870.     case 'D':        /* possible \Deleted flag */
  1871.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  1872.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  1873.       t += 7;        /* skip past flag name */
  1874.       break;
  1875.     case 'F':        /* possible \Flagged flag */
  1876.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  1877.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  1878.       t += 7;        /* skip past flag name */
  1879.       break;
  1880.     case 'A':        /* possible \Answered flag */
  1881.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  1882.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  1883.       t += 8;        /* skip past flag name */
  1884.       break;
  1885.     default:        /* unknown */
  1886.       i = 0;
  1887.       break;
  1888.     }
  1889.                 /* add flag to flags list */
  1890.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  1891.       }
  1892.       else {            /* no user flags yet */
  1893.     t = strtok (t," ");    /* isolate flag name */
  1894.     sprintf (err,"Unknown flag: %.80s",t);
  1895.     t = strtok (NIL," ");    /* get next flag */
  1896.     mm_log (err,ERROR);
  1897.       }
  1898.     }
  1899.   }
  1900.   return f;
  1901. }
  1902.  
  1903. /* Search support routines
  1904.  * Accepts: MAIL stream
  1905.  *        message number
  1906.  *        pointer to additional data
  1907.  * Returns: T if search matches, else NIL
  1908.  */
  1909.  
  1910.  
  1911. char mmdf_search_all (stream,msgno,d,n)
  1912.     MAILSTREAM *stream;
  1913.     long msgno;
  1914.     char *d;
  1915.     long n;
  1916. {
  1917.   return T;            /* ALL always succeeds */
  1918. }
  1919.  
  1920.  
  1921. char mmdf_search_answered (stream,msgno,d,n)
  1922.     MAILSTREAM *stream;
  1923.     long msgno;
  1924.     char *d;
  1925.     long n;
  1926. {
  1927.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1928. }
  1929.  
  1930.  
  1931. char mmdf_search_deleted (stream,msgno,d,n)
  1932.     MAILSTREAM *stream;
  1933.     long msgno;
  1934.     char *d;
  1935.     long n;
  1936. {
  1937.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1938. }
  1939.  
  1940.  
  1941. char mmdf_search_flagged (stream,msgno,d,n)
  1942.     MAILSTREAM *stream;
  1943.     long msgno;
  1944.     char *d;
  1945.     long n;
  1946. {
  1947.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1948. }
  1949.  
  1950.  
  1951. char mmdf_search_keyword (stream,msgno,d,n)
  1952.     MAILSTREAM *stream;
  1953.     long msgno;
  1954.     char *d;
  1955.     long n;
  1956. {
  1957.   return NIL;            /* keywords not supported yet */
  1958. }
  1959.  
  1960.  
  1961. char mmdf_search_new (stream,msgno,d,n)
  1962.     MAILSTREAM *stream;
  1963.     long msgno;
  1964.     char *d;
  1965.     long n;
  1966. {
  1967.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1968.   return (elt->recent && !elt->seen) ? T : NIL;
  1969. }
  1970.  
  1971. char mmdf_search_old (stream,msgno,d,n)
  1972.     MAILSTREAM *stream;
  1973.     long msgno;
  1974.     char *d;
  1975.     long n;
  1976. {
  1977.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1978. }
  1979.  
  1980.  
  1981. char mmdf_search_recent (stream,msgno,d,n)
  1982.     MAILSTREAM *stream;
  1983.     long msgno;
  1984.     char *d;
  1985.     long n;
  1986. {
  1987.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1988. }
  1989.  
  1990.  
  1991. char mmdf_search_seen (stream,msgno,d,n)
  1992.     MAILSTREAM *stream;
  1993.     long msgno;
  1994.     char *d;
  1995.     long n;
  1996. {
  1997.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1998. }
  1999.  
  2000.  
  2001. char mmdf_search_unanswered (stream,msgno,d,n)
  2002.     MAILSTREAM *stream;
  2003.     long msgno;
  2004.     char *d;
  2005.     long n;
  2006. {
  2007.   return mail_elt (stream,msgno)->answered ? NIL : T;
  2008. }
  2009.  
  2010.  
  2011. char mmdf_search_undeleted (stream,msgno,d,n)
  2012.     MAILSTREAM *stream;
  2013.     long msgno;
  2014.     char *d;
  2015.     long n;
  2016. {
  2017.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  2018. }
  2019.  
  2020.  
  2021. char mmdf_search_unflagged (stream,msgno,d,n)
  2022.     MAILSTREAM *stream;
  2023.     long msgno;
  2024.     char *d;
  2025.     long n;
  2026. {
  2027.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  2028. }
  2029.  
  2030.  
  2031. char mmdf_search_unkeyword (stream,msgno,d,n)
  2032.     MAILSTREAM *stream;
  2033.     long msgno;
  2034.     char *d;
  2035.     long n;
  2036. {
  2037.   return T;            /* keywords not supported yet */
  2038. }
  2039.  
  2040.  
  2041. char mmdf_search_unseen (stream,msgno,d,n)
  2042.     MAILSTREAM *stream;
  2043.     long msgno;
  2044.     char *d;
  2045.     long n;
  2046. {
  2047.   return mail_elt (stream,msgno)->seen ? NIL : T;
  2048. }
  2049.  
  2050. char mmdf_search_before (stream,msgno,d,n)
  2051.     MAILSTREAM *stream;
  2052.     long msgno;
  2053.     char *d;
  2054.     long n;
  2055. {
  2056.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  2057.   return (char) ((long) ((elt->year << 9) + (elt->month << 5) + elt->day) < n);
  2058. }
  2059.  
  2060.  
  2061. char mmdf_search_on (stream,msgno,d,n)
  2062.     MAILSTREAM *stream;
  2063.     long msgno;
  2064.     char *d;
  2065.     long n;
  2066. {
  2067.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  2068.   return (char) (((elt->year << 9) + (elt->month << 5) + elt->day) == n);
  2069. }
  2070.  
  2071.  
  2072. char mmdf_search_since (stream,msgno,d,n)
  2073.     MAILSTREAM *stream;
  2074.     long msgno;
  2075.     char *d;
  2076.     long n;
  2077. {
  2078.                 /* everybody interprets "since" as .GE. */
  2079.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  2080.   return (char)((long) ((elt->year << 9) + (elt->month << 5) + elt->day) >= n);
  2081. }
  2082.  
  2083.  
  2084. char mmdf_search_body (stream,msgno,d,n)
  2085.     MAILSTREAM *stream;
  2086.     long msgno;
  2087.     char *d;
  2088.     long n;
  2089. {
  2090.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  2091.   return search (m->body,m->bodysize,d,n);
  2092. }
  2093.  
  2094.  
  2095. char mmdf_search_subject (stream,msgno,d,n)
  2096.     MAILSTREAM *stream;
  2097.     long msgno;
  2098.     char *d;
  2099.     long n;
  2100. {
  2101.   char *s = mmdf_fetchstructure (stream,msgno,NIL)->subject;
  2102.   return s ? search (s,strlen (s),d,n) : NIL;
  2103. }
  2104.  
  2105.  
  2106. char mmdf_search_text (stream,msgno,d,n)
  2107.     MAILSTREAM *stream;
  2108.     long msgno;
  2109.     char *d;
  2110.     long n;
  2111. {
  2112.   FILECACHE *m = LOCAL->msgs[msgno - 1];
  2113.   return search (m->header,m->headersize,d,n) ||
  2114.     mmdf_search_body (stream,msgno,d,n);
  2115. }
  2116.  
  2117. char mmdf_search_bcc (stream,msgno,d,n)
  2118.     MAILSTREAM *stream;
  2119.     long msgno;
  2120.     char *d;
  2121.     long n;
  2122. {
  2123.   ADDRESS *a = mmdf_fetchstructure (stream,msgno,NIL)->bcc;
  2124.   LOCAL->buf[0] = '\0';        /* initially empty string */
  2125.                 /* get text for address */
  2126.   rfc822_write_address (LOCAL->buf,a);
  2127.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  2128. }
  2129.  
  2130.  
  2131. char mmdf_search_cc (stream,msgno,d,n)
  2132.     MAILSTREAM *stream;
  2133.     long msgno;
  2134.     char *d;
  2135.     long n;
  2136. {
  2137.   ADDRESS *a = mmdf_fetchstructure (stream,msgno,NIL)->cc;
  2138.   LOCAL->buf[0] = '\0';        /* initially empty string */
  2139.                 /* get text for address */
  2140.   rfc822_write_address (LOCAL->buf,a);
  2141.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  2142. }
  2143.  
  2144.  
  2145. char mmdf_search_from (stream,msgno,d,n)
  2146.     MAILSTREAM *stream;
  2147.     long msgno;
  2148.     char *d;
  2149.     long n;
  2150. {
  2151.   ADDRESS *a = mmdf_fetchstructure (stream,msgno,NIL)->from;
  2152.   LOCAL->buf[0] = '\0';        /* initially empty string */
  2153.                 /* get text for address */
  2154.   rfc822_write_address (LOCAL->buf,a);
  2155.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  2156. }
  2157.  
  2158.  
  2159. char mmdf_search_to (stream,msgno,d,n)
  2160.     MAILSTREAM *stream;
  2161.     long msgno;
  2162.     char *d;
  2163.     long n;
  2164. {
  2165.   ADDRESS *a = mmdf_fetchstructure (stream,msgno,NIL)->to;
  2166.   LOCAL->buf[0] = '\0';        /* initially empty string */
  2167.                 /* get text for address */
  2168.   rfc822_write_address (LOCAL->buf,a);
  2169.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  2170. }
  2171.  
  2172. /* Search parsers */
  2173.  
  2174.  
  2175. /* Parse a date
  2176.  * Accepts: function to return
  2177.  *        pointer to date integer to return
  2178.  * Returns: function to return
  2179.  */
  2180.  
  2181. search_t mmdf_search_date (f,n)
  2182.     search_t f;
  2183.     long *n;
  2184. {
  2185.   long i;
  2186.   char *s;
  2187.   MESSAGECACHE elt;
  2188.                 /* parse the date and return fn if OK */
  2189.   return (mmdf_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  2190.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  2191. }
  2192.  
  2193. /* Parse a flag
  2194.  * Accepts: function to return
  2195.  *        pointer to string to return
  2196.  * Returns: function to return
  2197.  */
  2198.  
  2199. search_t mmdf_search_flag (f,d)
  2200.     search_t f;
  2201.     char **d;
  2202. {
  2203.                 /* get a keyword, return if OK */
  2204.   return (*d = strtok (NIL," ")) ? f : NIL;
  2205. }
  2206.  
  2207.  
  2208. /* Parse a string
  2209.  * Accepts: function to return
  2210.  *        pointer to string to return
  2211.  *        pointer to string length to return
  2212.  * Returns: function to return
  2213.  */
  2214.  
  2215.  
  2216. search_t mmdf_search_string (f,d,n)
  2217.     search_t f;
  2218.     char **d;
  2219.     long *n;
  2220. {
  2221.   char *end = " ";
  2222.   char *c = strtok (NIL,"");    /* remainder of criteria */
  2223.   if (!c) return NIL;        /* missing argument */
  2224.   switch (*c) {            /* see what the argument is */
  2225.   case '{':            /* literal string */
  2226.     *n = strtol (c+1,d,10);    /* get its length */
  2227.     if ((*(*d)++ == '}') && (*(*d)++ == '\015') && (*(*d)++ == '\012') &&
  2228.     (!(*(c = *d + *n)) || (*c == ' '))) {
  2229.       char e = *--c;
  2230.       *c = DELIM;        /* make sure not a space */
  2231.       strtok (c," ");        /* reset the strtok mechanism */
  2232.       *c = e;            /* put character back */
  2233.       break;
  2234.     }
  2235.   case '\0':            /* catch bogons */
  2236.   case ' ':
  2237.     return NIL;
  2238.   case '"':            /* quoted string */
  2239.     if (strchr (c+1,'"')) end = "\"";
  2240.     else return NIL;
  2241.   default:            /* atomic string */
  2242.     if (*d = strtok (c,end)) *n = strlen (*d);
  2243.     else return NIL;
  2244.     break;
  2245.   }
  2246.   return f;
  2247. }
  2248.